﻿// Odatavalidator ver. 1.0 
// Copyright (c) Microsoft Corporation
// All rights reserved. 

// Licensed under the Apache License, Version 2.0 (the ""License"");
// you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at 
//
//           http://www.apache.org/licenses/LICENSE-2.0 

// THIS CODE IS PROVIDED ON AN  *AS IS* BASIS, WITHOUT WARRANTIES OR 
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
// LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS 
// FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 

// See the Apache Version 2.0 License for specific language governing 
// permissions and limitations under the License.

namespace ODataValidator.RuleEngine.Common
{
    #region Namespace.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Runtime.CompilerServices;
    #endregion

    /// <summary>
    /// The ServiceStatus class.
    /// </summary>
    public class ServiceStatus
    {
        /// <summary>
        /// Get the instance of class type ServiceStatus.
        /// </summary>
        /// <returns>The instance of class type TermDocument.</returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static ServiceStatus GetInstance(string url = null)
        {
            if (!string.IsNullOrEmpty(url))
            {
                if (null == serviceStatus || url != serviceStatus.rootURL)
                {
                    serviceStatus = new ServiceStatus(url);
                }
            }

            return serviceStatus;
        }

        /// <summary>
        /// Gets the service root URL.
        /// </summary>
        public string RootURL
        {
            get
            {
                return this.rootURL;
            }
        }

        /// <summary>
        /// Gets the service document.
        /// </summary>
        public string ServiceDocument
        {
            get
            {
                if (!this.serviceDoc.IsJsonPayload())
                {
                    throw new FormatException("The class member 'serviceDoc' does not store a JSON format data.");
                }

                return this.serviceDoc;
            }
        }

        /// <summary>
        /// Gets the metadata document.
        /// </summary>
        public string MetadataDocument
        {
            get
            {
                if (!this.metadataDoc.IsXmlPayload())
                {
                    throw new FormatException("The class member 'metadataDoc' does not store a XML format data.");
                }

                return this.metadataDoc;
            }
        }

        /// <summary>
        /// Gets or sets the request headers.
        /// </summary>
        public IEnumerable<KeyValuePair<string, string>> DefaultHeaders
        {
            get
            {
                return this.defaultHeaders;
            }
            set 
            {
                this.defaultHeaders = value;
            }
        }

        /// <summary>
        /// The service status.
        /// </summary>
        private static ServiceStatus serviceStatus;

        /// <summary>
        /// The service root URL.
        /// </summary>
        private string rootURL;

        /// <summary>
        /// The service document.
        /// </summary>
        private string serviceDoc;

        /// <summary>
        /// The metadata document.
        /// </summary>
        private string metadataDoc;

        /// <summary>
        /// The default request headers.
        /// </summary>
        private IEnumerable<KeyValuePair<string, string>> defaultHeaders;

        /// <summary>
        /// The constructor of class type ServiceStatus.
        /// </summary>
        private ServiceStatus(string url)
        {
            var uri = this.ConvertToUri(url);
            defaultHeaders = new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("ODataVersion", "4.0") };

            // Get the service document.
            if (this.GetServiceDocument(uri, out this.rootURL, out this.serviceDoc))
            {
                // Get the metadata document.
                string metadataURL = string.Format("{0}/$metadata", this.rootURL);
                var response = WebHelper.Get(new Uri(metadataURL), string.Empty, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, null);
                this.metadataDoc = HttpStatusCode.OK == response.StatusCode ? response.ResponsePayload : string.Empty;
            }
        }

        /// <summary>
        /// Get the service document.
        /// </summary>
        /// <param name="uri">The uri inputted by the user.</param>
        /// <param name="rootURL">The service root URL which is outputted by the program.</param>
        /// <param name="svcDoc">The service document which is outputted by the program.</param>
        /// <returns>Returns the boolean value to indicate whether the method has been got a service document or not.</returns>
        private bool GetServiceDocument(Uri uri, out string rootURL, out string svcDoc)
        {
            rootURL = string.Empty;
            svcDoc = string.Empty;
            if (null == uri || !uri.IsAbsoluteUri)
            {
                return false;
            }

            var resp = WebHelper.Get(uri, string.Empty, RuleEngineSetting.Instance().DefaultMaximumPayloadSize, null);
            if (null != resp && HttpStatusCode.OK == resp.StatusCode)
            {
                if (resp.IsServiceDocument())
                {
                    this.rootURL = uri.ToString();
                    this.serviceDoc = resp.ResponsePayload;

                    return true;
                }
                else
                {
                    var segments = uri.Segments;
                    if (null != segments && segments.Length > 0)
                    {
                        Uri parentUri;
                        if (Uri.TryCreate(uri.GetLeftPart(UriPartial.Authority) + string.Join(string.Empty, segments.Take(segments.Length - 1).ToArray()), UriKind.Absolute, out parentUri))
                        {
                            if (GetServiceDocument(parentUri, out rootURL, out svcDoc))
                            {
                                return true;
                            }
                        }
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Convert a URL string to an URI.
        /// </summary>
        /// <param name="url">A URL string.</param>
        /// <returns>Returns an matched URI.</returns>
        private Uri ConvertToUri(string url)
        {
            Uri uri;

            try
            {
                uri = new Uri(url.TrimEnd('/'));
            }
            catch (UriFormatException)
            {
                if (!url.StartsWith("http://") && !url.StartsWith("https://"))
                {
                    uri = new Uri(SupportedScheme.SchemeHttp + "://" + url);
                }
                else
                {
                    uri = new Uri(Uri.EscapeUriString(url));
                }
            }

            return uri;
        }
    }
}
